/*
 * Middleware
 * Copyright (C) 2010/11
 * Riccardo Pulvirenti, Giuseppe Ravidà, Andrea Tino
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANDABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses
 */

/*!
 * File: core.cpp
 */

#include "core.hpp"
#include "ui.hpp"
#include "manager.hpp"
#include "worker_discovery.hpp"
#include "worker.hpp"
#include "utils.hpp"
#include "data.hpp"

using namespace middleware;

//-----------------------------------------------------------------------------
// Constructors and destructor
/* Default constructor */
CORE::CORE() {
	PeerConfiguration pc = CORE::GetPeerConfiguration(kXmlConfigFileName);
	this->_config_filename = kXmlConfigFileName;
	this->_logger_postfix = utils::GetRandomIntegralNumberStr(1, 10000);
	this->_xml_logger = new logging::XmlSynLogger(kLogFileName,
			this->_logger_postfix,
			pc.LogCTAPort);
}
/* Constructor specifying file name */
CORE::CORE(const std::string& config_filename, const std::string& config_peerid) {
	PeerConfiguration pc = CORE::GetPeerConfiguration(config_filename);
	this->_config_filename = config_filename;
	this->_logger_postfix = config_peerid;
	this->_xml_logger = new logging::XmlSynLogger(kLogFileName,
			this->_logger_postfix,
			pc.LogCTAPort);
}
/* Destructor */
CORE::~CORE() {
	if (this->_xml_logger != 0) delete this->_xml_logger;
}
//-----------------------------------------------------------------------------
// Public members
/* Execution for core (NOT A THREAD) */
void CORE::exec() {
	std::cout << kCOREEtq << "CORE now started!" << std::endl;
	try {
		// Bonjour
		this->bonjour();
		// Loading configuration ----------------------------------------------
		std::cout << kCOREEtq << "Loading configuration..." << std::endl;
		PeerConfiguration pc = CORE::GetPeerConfiguration(this->_config_filename);
		std::cout << kCOREEtq << "Configuration loaded!" << std::endl;
		// Managing knowledge -------------------------------------------------
		std::cout << kCOREEtq << "Initializing Peers Knowledge..." << std::endl;
		this->_peers_worker_descriptors.clear();
		WorkerCapabilities supp_wc; /* Automatically default (null) generated */
		supp_wc = kDefWorkerCapab;
		std::vector< Address >::iterator peers_addr_it =
				pc.PeerNeighbourhood.begin();
		for (peers_addr_it = pc.PeerNeighbourhood.begin();
				peers_addr_it != pc.PeerNeighbourhood.end(); peers_addr_it++) {
			Address supp_addr = (*peers_addr_it);
			WorkerDescriptor supp_wd(supp_addr, supp_wc);
			std::cout << kCOREEtq << "Inserted known peer in PK: IP=" <<
					supp_addr.get_ip() << " manPORT=" << supp_addr.get_port() <<
					" discPORT=" << supp_addr.get_port_disc() <<
					" wPORT=" << supp_addr.get_port_w() << std::endl;
			this->_peers_worker_descriptors.push_back(supp_wd); /* First */
		}
		// Knowledge reference
		PeersKnowledge PK;
		PK.ptr_to_worker_descriptors = &(this->_peers_worker_descriptors);
		PK.ptr_to_worker_descriptors_mutex =
				&(this->_peers_worker_descriptors_mutex);
		// Launch threads
		/* DISC ------------------------------------------------------------ */
		DiscoveryConfig discconf;
		std::cout << kCOREEtq << "Configuring Discovery..." << std::endl;
		discconf.disc_ip_tow_bind = boost::lexical_cast<std::string>(
				pc.PeerAddress.get_ip());
		discconf.disc_port_tow_bind = boost::lexical_cast<std::string>(
				pc.PeerAddress.get_port_disc());
		discconf._xml_logger = this->_xml_logger;
		WorkerDiscovery disc(PK, discconf);
		/* W --------------------------------------------------------------- */
		Address addr = pc.PeerAddress; /* For WorkerDescriptor */
		WorkerCapabilities capab;  /* For WorkerDescriptor */
		std::cout << kCOREEtq << "Configuring Worker..." << std::endl;
		capab.capab_bandwidth = pc.PeerCapabilities.capab_bandwidth;
		capab.capab_core_num = pc.PeerCapabilities.capab_core_num;
		capab.capab_cpu_mhz = pc.PeerCapabilities.capab_cpu_mhz;
		capab.capab_cpu_num = pc.PeerCapabilities.capab_cpu_num;
		capab.capab_hops = pc.PeerCapabilities.capab_hops;
		capab.capab_ram_mb = pc.PeerCapabilities.capab_ram_mb;
		WorkerDescriptor wd(addr, capab);
		WConfig wconf;
		wconf._xml_logger = this->_xml_logger;
		Worker worker(wconf, PK, wd);
		/* MAN ------------------------------------------------------------- */
		std::cout << kCOREEtq << "Configuring Manager..." << std::endl;
		ManagerConfig manconf;
		manconf.man_ip_toman_bind = boost::lexical_cast<std::string>(
				pc.PeerAddress.get_ip());
		manconf.man_port_toman_bind = boost::lexical_cast<std::string>(
				pc.PeerAddress.get_port());
		manconf.ptrto_worker = &worker;
		manconf.ptrto_discovery = &disc;
		manconf._xml_logger = this->_xml_logger;
		Manager man(manconf);
		/* UI -------------------------------------------------------------- */
		std::cout << kCOREEtq << "Configuring UI..." << std::endl;
		UIConfig uiconf;
		uiconf.ptrto_manager = &man;
		uiconf._xml_logger = this->_xml_logger;
		UI ui(uiconf);
		// Running ------------------------------------------------------------
		std::cout << kCOREEtq << "Running main components..." << std::endl;
		man.run();
		ui.run();
		worker.run();
		disc.run();
		std::cout << kCOREEtq << "All components running!" << std::endl;
		// Joining ------------------------------------------------------------
		std::cout << kCOREEtq << "Joining on main components..." << std::endl;
		man.join();
		ui.join();
		worker.join();
		disc.join();
	} catch(CoreException& e) {
		std::cout << kCOREEtq << "Top Level Error: " << e.what() << std::endl;
		std::cout << kCOREEtq << "Now exiting!" << std::endl;
	} catch(std::exception e) {
		std::cout << kCOREEtq << "Top Level Error: " << e.what() << std::endl;
		std::cout << kCOREEtq << "Now exiting!" << std::endl;
	}
	std::cout << kCOREEtq << "Exiting..." << std::endl;
}
/* Gets configuration */
PeerConfiguration CORE::GetPeerConfiguration(std::string config_file)
		throw(CoreException) {
	using namespace xercesc;
	try {
		PeerConfiguration pc;
		XercesDOMParser* parser = new XercesDOMParser();
		parser->setValidationScheme(xercesc::XercesDOMParser::Val_Never);
		parser->setDoSchema(false);
		parser->setLoadExternalDTD(false);
		parser->parse(config_file.c_str());
		DOMDocument* doc = parser->getDocument();
		DOMElement* root = doc->getDocumentElement();
		// This peer
		DOMElement* node_thispeer = dynamic_cast<DOMElement*>(
				root->getElementsByTagName(
				XMLString::transcode("ThisPeer"))->item(0));
		DOMElement* node_thispeer_peerid =
				dynamic_cast<DOMElement*>(node_thispeer->getElementsByTagName(
				XMLString::transcode("PeerId"))->item(0));
		DOMElement* node_thispeer_netint = dynamic_cast<DOMElement*>(
				node_thispeer->getElementsByTagName(
				XMLString::transcode("NetworkInterface"))->item(0));
		DOMElement* node_thispeer_netint_ip =
				dynamic_cast<DOMElement*>(node_thispeer_netint->getElementsByTagName(
				XMLString::transcode("IpAddress"))->item(0));
		DOMElement* node_thispeer_netint_porttoman =
				dynamic_cast<DOMElement*>(node_thispeer_netint->getElementsByTagName(
				XMLString::transcode("PortToManager"))->item(0));
		DOMElement* node_thispeer_netint_porttodisc =
				dynamic_cast<DOMElement*>(node_thispeer_netint->getElementsByTagName(
				XMLString::transcode("PortToDiscovery"))->item(0));
		DOMElement* node_thispeer_netint_porttoworker =
				dynamic_cast<DOMElement*>(node_thispeer_netint->getElementsByTagName(
				XMLString::transcode("PortToWorker"))->item(0));
		DOMElement* node_thispeer_logctaport =
				dynamic_cast<DOMElement*>(node_thispeer->getElementsByTagName(
				XMLString::transcode("LogCentralizedTokenAgreementPort"))->item(0));
		DOMElement* node_thispeer_capab =
				dynamic_cast<DOMElement*>(node_thispeer->getElementsByTagName(
				XMLString::transcode("Capabilities"))->item(0));
		DOMElement* node_thispeer_capab_cpunum =
				dynamic_cast<DOMElement*>(node_thispeer_capab->getElementsByTagName(
				XMLString::transcode("CpuNumber"))->item(0));
		DOMElement* node_thispeer_capab_corenum =
				dynamic_cast<DOMElement*>(node_thispeer_capab->getElementsByTagName(
				XMLString::transcode("CoreNumber"))->item(0));
		DOMElement* node_thispeer_capab_band =
				dynamic_cast<DOMElement*>(node_thispeer_capab->getElementsByTagName(
				XMLString::transcode("BandwidthKhz"))->item(0));
		DOMElement* node_thispeer_capab_cpuspeed =
				dynamic_cast<DOMElement*>(node_thispeer_capab->getElementsByTagName(
				XMLString::transcode("CpuSpeedMhz"))->item(0));
		DOMElement* node_thispeer_capab_dist =
				dynamic_cast<DOMElement*>(node_thispeer_capab->getElementsByTagName(
				XMLString::transcode("DistanceHops"))->item(0));
		DOMElement* node_thispeer_capab_ram =
				dynamic_cast<DOMElement*>(node_thispeer_capab->getElementsByTagName(
				XMLString::transcode("RamMb"))->item(0));
		// Neighbourhood
		std::vector< Address > neigh_peers;
		DOMNodeList* neigh;
		DOMElement* node_neighpeers =
				dynamic_cast<DOMElement*>(root->getElementsByTagName(
				XMLString::transcode("NeighbourPeers"))->item(0));
		neigh = node_neighpeers->getElementsByTagName(
				XMLString::transcode("NeighbourPeer"));
		for (int i = 0; i < neigh->getLength(); i++) {
			DOMElement* neighel = dynamic_cast<DOMElement*>(neigh->item(i));
			DOMElement* neighel_netint =
				dynamic_cast<DOMElement*>(neighel->getElementsByTagName(
				XMLString::transcode("NetworkInterface"))->item(0));
			DOMElement* neighel_netint_ip =
				dynamic_cast<DOMElement*>(neighel_netint->getElementsByTagName(
				XMLString::transcode("IpAddress"))->item(0));
			DOMElement* neighel_netint_porttoman =
				dynamic_cast<DOMElement*>(neighel_netint->getElementsByTagName(
				XMLString::transcode("PortToManager"))->item(0));
			DOMElement* neighel_netint_porttodisc =
				dynamic_cast<DOMElement*>(neighel_netint->getElementsByTagName(
				XMLString::transcode("PortToDiscovery"))->item(0));
			DOMElement* neighel_netint_porttoworker =
				dynamic_cast<DOMElement*>(neighel_netint->getElementsByTagName(
				XMLString::transcode("PortToWorker"))->item(0));
			Address addr;
			addr.set_ip(boost::lexical_cast<InetIpAddr>(XMLString::transcode(neighel_netint_ip->getTextContent())));
			addr.set_port(boost::lexical_cast<unsigned int>(XMLString::transcode(neighel_netint_porttoman->getTextContent())));
			addr.set_port_disc(boost::lexical_cast<unsigned int>(XMLString::transcode(neighel_netint_porttodisc->getTextContent())));
			addr.set_port_w(boost::lexical_cast<unsigned int>(XMLString::transcode(neighel_netint_porttoworker->getTextContent())));
			neigh_peers.push_back(addr);
		}
		// Saving
		pc.PeerId = boost::lexical_cast<std::string>(XMLString::transcode(node_thispeer_peerid->getTextContent()));
		pc.PeerAddress.set_ip(boost::lexical_cast<InetIpAddr>(XMLString::transcode(node_thispeer_netint_ip->getTextContent())));
		pc.PeerAddress.set_port(boost::lexical_cast<InetPort>(XMLString::transcode(node_thispeer_netint_porttoman->getTextContent())));
		pc.PeerAddress.set_port_disc(boost::lexical_cast<InetPort>(XMLString::transcode(node_thispeer_netint_porttodisc->getTextContent())));
		pc.PeerAddress.set_port_w(boost::lexical_cast<InetPort>(XMLString::transcode(node_thispeer_netint_porttoworker->getTextContent())));
		pc.LogCTAPort = boost::lexical_cast<CTAPort>(XMLString::transcode(node_thispeer_logctaport->getTextContent()));
		pc.PeerCapabilities.capab_cpu_num = boost::lexical_cast<unsigned int>(XMLString::transcode(node_thispeer_capab_cpunum->getTextContent()));
		pc.PeerCapabilities.capab_core_num = boost::lexical_cast<unsigned int>(XMLString::transcode(node_thispeer_capab_corenum->getTextContent()));
		pc.PeerCapabilities.capab_bandwidth = boost::lexical_cast<double>(XMLString::transcode(node_thispeer_capab_band->getTextContent()));
		pc.PeerCapabilities.capab_cpu_mhz = boost::lexical_cast<double>(XMLString::transcode(node_thispeer_capab_cpuspeed->getTextContent()));
		pc.PeerCapabilities.capab_hops = boost::lexical_cast<unsigned int>(XMLString::transcode(node_thispeer_capab_dist->getTextContent()));
		pc.PeerCapabilities.capab_ram_mb = boost::lexical_cast<double>(XMLString::transcode(node_thispeer_capab_ram->getTextContent()));
		std::vector< Address >::iterator it = neigh_peers.begin();
		for (it = neigh_peers.begin(); it != neigh_peers.end(); it++) {
			pc.PeerNeighbourhood.push_back(*it);
		}
		delete parser;
		return pc;
	} catch (std::exception& e) {
		std::string p = std::string("Problems loading configuration! ") +
				std::string(e.what());
		throw CoreException(p);
	}
}
/* Printing peer configuration */
std::string CORE::PrintPeerConfiguration(std::string config_file) throw(CoreException) {
	PeerConfiguration pc = CORE::GetPeerConfiguration(config_file);
	std::cout << "Printing loaded configuration for this peer" << std::endl;
	std::cout << "This peer ID: " << pc.PeerId << std::endl;
	std::cout << "This peer network interface: " << pc.PeerAddress.get_ip() <<
			" - " << pc.PeerAddress.get_port() <<
			" - " << pc.PeerAddress.get_port_disc() <<
			" - " << pc.PeerAddress.get_port_w() << std::endl;
	std::cout << "Port for CTA: " << pc.LogCTAPort << std::endl;
	std::cout << "Capabilites for this peer: " <<
			"CpuNum: " << pc.PeerCapabilities.capab_cpu_num << " - " <<
			"CoreNum: " << pc.PeerCapabilities.capab_core_num << " - " <<
			"CpuSpeed: " << pc.PeerCapabilities.capab_cpu_mhz << " - " <<
			"Band: " << pc.PeerCapabilities.capab_bandwidth << " - " <<
			"Hops: " << pc.PeerCapabilities.capab_hops << " - " <<
			"Ram: " << pc.PeerCapabilities.capab_ram_mb << std::endl;
	std::cout << "Listing neighbourhood:" << std::endl;
	std::vector< Address >::iterator it = pc.PeerNeighbourhood.begin();
	for (it = pc.PeerNeighbourhood.begin();
			it != pc.PeerNeighbourhood.end(); it++) {
		std::cout << "Neigh located at: " << it->get_ip() << " - " <<
				it->get_port() << " - " << it->get_port_disc() << " - " <<
				it->get_port_w() << std::endl;
	}
	return pc.PeerId;
}
//-----------------------------------------------------------------------------
// Private members
/* Recoverer */
void CORE::exec_recovery(InetPort recovery_port) {

}
/* Bonjour */
void CORE::bonjour() throw(CoreException) {
	std::cout << kCOREEtq << "Initialization sequence now started..." <<
				std::endl;
	std::cout << kCOREEtq << "Checking configuration file: " <<
			this->_config_filename << " (local path)..." << std::endl;
	try {
		this->CheckConfigFile();
	} catch(CoreException& e) {
		std::cout << "Could not open configuration file!" <<
				std::endl;
		throw CoreException("Incorrect configuration file.");
	}
	std::cout << kCOREEtq << "Configuration file ok!" << std::endl;
	std::cout << kCOREEtq << "Initialization completed!" << std::endl;
}
/* File opener */
void CORE::CheckConfigFile() throw(CoreException) {
	std::ifstream config_file;
	config_file.open(this->_config_filename.c_str());
	if (!(config_file.is_open()))
		throw CoreException("Unable to open configuration file!");
	config_file.close();
}
